﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Mapping.ByCode;
using Xenta.Enums;
using Xenta.Utils;

namespace Xenta.Entities
{
    /// <summary>
    /// Represents the publication entity.
    /// </summary>
    /// <remarks><pre>
    /// The entity regarding the disclosure level.
    /// 
    /// + Entire
    ///  + Extended
    ///   | - Tags(of the Extended level)
    ///   + Base
    ///    | - Author(of the Base level)
    ///    | - FileGroup(of the Primary level)
    ///    | - RatingCount
    ///    | - RatingAverage
    ///    | - CommentCount
    ///    | - Flags
    ///    | - PublishedOn
    ///    | - UpdatedOn
    ///    | - CreatedOn
    ///    | - Translations(of the same level)
    ///    + Keys
    ///     | - Code
    ///     + Primary
    ///      | - EntityID
    /// 
    /// Note: upper level includes lower level.
    /// </pre></remarks>
    public class PublicationEntity : AuditableEntity,
        IRelationHolder<PublicationTagEntity>,
        IRelationHolder<LookupAttributeEntity>
    {
        #region Fields

        private FileGroupEntity _fileGroup;
        private DateTime _publishedOn;
        private ICollection<PublicationRatingEntity> _ratings;
        private ICollection<PublicationCommentEntity> _comments;
        private ICollection<TPublicationEntity> _translations;
        private ICollection<PublicationTagEntity> _tags;
        private ICollection<LookupAttributeEntity> _attributes;

        #endregion

        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public PublicationEntity()
        {
            _fileGroup = null;
            _publishedOn = DateTime.UtcNow;
            _ratings = new List<PublicationRatingEntity>();
            _comments = new List<PublicationCommentEntity>();
            _translations = new List<TPublicationEntity>();
            _tags = new List<PublicationTagEntity>();
            _attributes = new List<LookupAttributeEntity>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the author.
        /// </summary>
        public virtual PublicationAuthorEntity Author
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the file group identifier.
        /// </summary>
        public virtual int FileGroupID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the file group.
        /// </summary>
        public virtual FileGroupEntity FileGroup
        {
            get
            {
                return FileGroupID != 0 ? _fileGroup : null;
            }
            set
            {
                _fileGroup = value;
                FileGroupID = value != null ? value.EntityID : 0;
            }
        }

        /// <summary>
        /// Gets or sets the code.
        /// </summary>
        public virtual string Code
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the numer of ratings.
        /// </summary>
        public virtual int RatingCount
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the average rating.
        /// </summary>
        public virtual byte RatingAverage
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the numer of comments.
        /// </summary>
        public virtual int CommentCount
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public virtual PublicationFlags Flags
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the publishing date.
        /// </summary>
        public virtual DateTime PublishedOn
        {
            get
            {
                return _publishedOn;
            }
            set
            {
                _publishedOn = value;
            }
        }

        #endregion

        #region Collections

        /// <summary>
        /// Gets the tags.
        /// </summary>
        public virtual ICollection<PublicationTagEntity> Tags
        {
            get
            {
                return _tags;
            }
            set
            {
                _tags = value;
            }
        }

        /// <summary>
        /// Gets the attributes.
        /// </summary>
        public virtual ICollection<LookupAttributeEntity> Attributes
        {
            get
            {
                return _attributes;
            }
            set
            {
                _attributes = value;
            }
        }

        /// <summary>
        /// Gets or sets the publication translations.
        /// </summary>
        public virtual ICollection<TPublicationEntity> Translations
        {
            get
            {
                return _translations;
            }
            set
            {
                _translations = value;
            }
        }
        
        /// <summary>
        /// Gets the ratings.
        /// </summary>
        public virtual ICollection<PublicationRatingEntity> Ratings
        {
            get
            {
                return _ratings;
            }
            set
            {
                _ratings = value;
            }
        }

        /// <summary>
        /// Gets the comments.
        /// </summary>
        public virtual ICollection<PublicationCommentEntity> Comments
        {
            get
            {
                return _comments;
            }
            set
            {
                _comments = value;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets the publication translation.
        /// </summary>
        /// <param name="languageID">The language identifier.</param>
        /// <returns>The publication translation.</returns>
        public virtual TPublicationEntity Translation(int languageID)
        {
            return _translations.SingleOrDefault(x => x.Language.EntityID == languageID);
        }

        /// <summary>
        /// Maps a tag to the publication.
        /// </summary>
        /// <param name="child">The tag to map.</param>
        public virtual void Map(PublicationTagEntity child)
        {
            if(Tags.Contains(child))
                return;
            Tags.Add(child);
        }

        /// <summary>
        /// Unmaps the tag from the publication.
        /// </summary>
        /// <param name="child">The tag to unmap.</param>
        public virtual void Unmap(PublicationTagEntity child)
        {
            if(!Tags.Contains(child))
                return;
            Tags.Remove(child);
        }

        /// <summary>
        /// Maps a tag to the publication.
        /// </summary>
        /// <param name="child">The tag to map.</param>
        public virtual void Map(LookupAttributeEntity child)
        {
            if(Attributes.Contains(child))
                return;
            Attributes.Add(child);
        }

        /// <summary>
        /// Unmaps the tag from the publication.
        /// </summary>
        /// <param name="child">The tag to unmap.</param>
        public virtual void Unmap(LookupAttributeEntity child)
        {
            if(!Attributes.Contains(child))
                return;
            Attributes.Remove(child);
        }

        #endregion

        #region Utilities

        protected override void Disclose(IDictionary<String, Object> obj,
            DisclosureLevel level,
            IDictionary<String, String> options)
        {
            base.Disclose(obj, level, options);
            if(level >= DisclosureLevel.Keys)
                obj["Code"] = Code;
            if(level >= DisclosureLevel.Base)
            {
                obj["Author"] = Author.Disclose(DisclosureLevel.Base, options);
                obj["FileGroup"] = FileGroup != null
                    ? FileGroup.Disclose(DisclosureLevel.Primary, options)
                    : null;
                obj["RatingCount"] = RatingCount;
                obj["RatingAverage"] = RatingAverage;
                obj["CommentCount"] = CommentCount;
                obj["Flags"] = Flags.ToString();
                obj["PublishedOn"] = PublishedOn;
                obj["Translations"] = (options.ContainsKey("TranslatedTo")
                    ? Translations.Where(x => options["TranslatedTo"].Contains(x.Language.Code))
                    : Translations)
                    .Map(x => x.Disclose(level, options));
            }
            if(level >= DisclosureLevel.Extended)
                obj["Tags"] = Tags.Map(x => x.Disclose(DisclosureLevel.Extended, options));
            if(level >= DisclosureLevel.Entire)
                obj["Attributes"] = Attributes.Map(x => x.Disclose(DisclosureLevel.Extended, options));
        }

        #endregion
    }

    /// <summary>
    /// The entity NHibernate mapping.
    /// </summary>
    public class PublicationMapping : EntityMapping<PublicationEntity>
    {
        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public PublicationMapping()
        {
            Table("fwk_Publications");
            Id(x => x.EntityID, m =>
            {
                m.Column("PublicationID");
                m.Generator(Generators.Identity);
            });
            ManyToOne(x => x.Author, m =>
            {
                m.Column("AuthorID");
                m.ForeignKey("FK_fwk_Publications_fwk_PublicationAuthors");
                m.NotNullable(true);
                m.Fetch(FetchKind.Join);
            });
            Property(x => x.FileGroupID, m =>
            {
                m.Column("FileGroupID");
                m.NotNullable(true);
            });
            ManyToOne(x => x.FileGroup, m =>
            {
                m.Column("FileGroupID");
                m.NotFound(NotFoundMode.Ignore);
                m.Insert(false);
                m.Update(false);
            });
            Property(x => x.Code, m =>
            {
                m.Unique(true);
                m.Index("IX_fwk_Publications");
                m.NotNullable(true);
                m.Length(10);
                m.Column(c =>
                {
                    c.SqlType("nvarchar(10)");
                });
            });
            Property(x => x.RatingCount, m =>
            {
                m.NotNullable(true);
            });
            Property(x => x.RatingAverage, m =>
            {
                m.NotNullable(true);
            });
            Property(x => x.CommentCount, m =>
            {
                m.NotNullable(true);
            });
            Property(x => x.Flags, m =>
            {
                m.NotNullable(true);
            });
            Property(x => x.PublishedOn, m =>
            {
                m.NotNullable(true);
            });
            Property(x => x.CreatedOn, m =>
            {
                m.NotNullable(true);
            });
            Property(x => x.UpdatedOn, m =>
            {
                m.NotNullable(true);
            });
            Bag(x => x.Tags, m =>
            {
                m.Table("fwk_MPublicationTags");
                m.Key(k =>
                {
                    k.Column("PublicationID");
                    k.ForeignKey("FK_fwk_MPublicationTags_fwk_Publications");
                    k.NotNullable(true);
                });
            },
            a =>
            {
                a.ManyToMany(m =>
                {
                    m.Column("TagID");
                    m.ForeignKey("FK_fwk_MPublicationTags_fwk_PublicationTags");
                });
            });
            Bag(x => x.Attributes, m =>
            {
                m.Table("fwk_MPublicationAttributes");
                m.Key(k =>
                {
                    k.Column("PublicationID");
                    k.ForeignKey("FK_fwk_MPublicationAttributes_fwk_Publications");
                    k.NotNullable(true);
                });
            },
            a =>
            {
                a.ManyToMany(m =>
                {
                    m.Column("AttributeID");
                    m.ForeignKey("FK_fwk_MPublicationAttributes_fwk_LookupAttributes");
                });
            });
            Bag(x => x.Translations, m =>
            {
                m.Table("fwk_TPublications");
                m.Key(k =>
                {
                    k.Column("PublicationID");
                    k.ForeignKey("FK_fwk_TPublications_fwk_Publications");
                    k.NotNullable(true);
                });
                m.Inverse(true);
                m.BatchSize(10);
            },
            a =>
            {
                a.OneToMany();
            });
            Bag(x => x.Ratings, m =>
            {
                m.Table("fwk_Ratings");
                m.Key(k =>
                {
                    k.Column("HolderID");
                    k.NotNullable(true);
                });
                m.Inverse(true);
            },
            a =>
            {
                a.OneToMany();
            });
            Bag(x => x.Comments, m =>
            {
                m.Table("fwk_Comments");
                m.Key(k =>
                {
                    k.Column("HolderID");
                    k.NotNullable(true);
                });
                m.Inverse(true);
            },
            a =>
            {
                a.OneToMany();
            });
        }
    }
}
